package xin.petstore.framework.orm.mybatis.paginator.dialect;

import xin.petstore.framework.orm.mybatis.paginator.domain.Order;
import xin.petstore.framework.orm.mybatis.paginator.domain.PageBounds;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMapping.Builder;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.reflection.wrapper.ObjectWrapper;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.SimpleTypeRegistry;
import org.apache.ibatis.type.TypeHandlerRegistry;

public class Dialect {
    protected TypeHandlerRegistry typeHandlerRegistry;
    protected MappedStatement mappedStatement;
    protected PageBounds pageBounds;
    protected Object parameterObject;
    protected BoundSql boundSql;
    protected List<ParameterMapping> parameterMappings;
    protected Map<String, Object> pageParameters = new HashMap();
    private String pageSQL;
    private String countSQL;

    public Dialect(MappedStatement mappedStatement, Object parameterObject, PageBounds pageBounds) {
        this.mappedStatement = mappedStatement;
        this.parameterObject = parameterObject;
        this.pageBounds = pageBounds;
        this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();

        init();
    }

    protected void init() {
        this.boundSql = this.mappedStatement.getBoundSql(this.parameterObject);
        this.parameterMappings = new ArrayList(this.boundSql.getParameterMappings());
        if ((this.parameterObject instanceof Map)) {
            this.pageParameters.putAll((Map) this.parameterObject);
        } else if (this.parameterObject != null) {
            Class cls = this.parameterObject.getClass();
            if ((cls.isPrimitive()) || (cls.isArray()) ||
                    (SimpleTypeRegistry.isSimpleType(cls)) ||
                    (Enum.class.isAssignableFrom(cls)) ||
                    (Collection.class.isAssignableFrom(cls))) {
                for (ParameterMapping parameterMapping : this.parameterMappings)
                    this.pageParameters.put(parameterMapping.getProperty(), this.parameterObject);
            } else {
                MetaObject metaObject = this.mappedStatement.getConfiguration().newMetaObject(this.parameterObject);
                ObjectWrapper wrapper = metaObject.getObjectWrapper();
                for (ParameterMapping parameterMapping : this.parameterMappings) {
                    PropertyTokenizer prop = new PropertyTokenizer(parameterMapping.getProperty());
                    this.pageParameters.put(parameterMapping.getProperty(), wrapper.get(prop));
                }
            }

        }

        StringBuffer bufferSql = new StringBuffer(this.boundSql.getSql().trim());
        if (bufferSql.lastIndexOf(";") == bufferSql.length() - 1) {
            bufferSql.deleteCharAt(bufferSql.length() - 1);
        }
        String sql = bufferSql.toString();
        this.pageSQL = sql;
        if ((this.pageBounds.getOrders() != null) && (!this.pageBounds.getOrders().isEmpty())) {
            this.pageSQL = getSortString(sql, this.pageBounds.getOrders());
        }
        if ((this.pageBounds.getOffset() != 0) ||
                (this.pageBounds.getLimit() != 2147483647)) {
            this.pageSQL = getLimitString(this.pageSQL, "__offset", this.pageBounds.getOffset(), "__limit", this.pageBounds.getLimit());
        }

        this.countSQL = getCountString(sql);
    }

    public List<ParameterMapping> getParameterMappings() {
        return this.parameterMappings;
    }

    public Object getParameterObject() {
        return this.pageParameters;
    }

    public String getPageSQL() {
        return this.pageSQL;
    }

    protected void setPageParameter(String name, Object value, Class type) {
        ParameterMapping parameterMapping = new ParameterMapping.Builder(this.mappedStatement.getConfiguration(), name, type).build();
        this.parameterMappings.add(parameterMapping);
        this.pageParameters.put(name, value);
    }

    public String getCountSQL() {
        return this.countSQL;
    }

    protected String getLimitString(String sql, String offsetName, int offset, String limitName, int limit) {
        throw new UnsupportedOperationException("paged queries not supported");
    }

    protected String getCountString(String sql) {
        return "select count(1) from (" + sql + ") tmp_count";
    }

    protected String getSortString(String sql, List<Order> orders) {
        if ((orders == null) || (orders.isEmpty())) {
            return sql;
        }

        StringBuffer buffer = new StringBuffer("select * from (").append(sql).append(") temp_order order by ");
        for (Order order : orders) {
            if (order == null) continue;
            buffer.append(order.toString())
                    .append(", ");
        }

        buffer.delete(buffer.length() - 2, buffer.length());
        return buffer.toString();
    }
}
